/**
 * 匿名管道通信：只用于有血缘关系的进程之间的通信的，即父子进程之间通信。
 * 实现父子进程间的数据传输
 * 父进程从管道文件进行读，子进程从管道文件进行写
 * （1）利用pipe()函数创建匿名管道，参数是一个输出型参数，下标0是读端，下标1是写端
 * （2）然后创建子进程
 * （3）在子进程中关闭不需要的那一端，父进程也是
 * （4）在子进程中进行数据写入，每隔1秒写一条数据，总共写10条数据
 * （5）在父进程中进行读取，一直读取
 * **/

/**
 * 通过下面代码测试：可以知道管道的四种特殊的情况
 * （1）写端不写（写段一开始睡眠一段时间来模拟写端不写），读端一直读（循环的读取）
 * 此时管道里面没有数据可以读取，因此对应的读端进程会阻塞到read处，被挂起，
 * 等待有数据写入后会被唤醒
 *
 * （2）读端不读（读端在读之前sleep一段时间来模拟），写端一直写，此时管道很快被写满
 * ，写端进程就会被挂起，等到管道数据被读取后就会被唤醒
 *
 * （3）写端，写完后关闭，读端依旧读取数据，此时读端读完后，并不会影响读端进程后面的执行
 *
 * （4）写端还在写，但是读端提前关闭，此时操作系统会把写端进程直接kill，因为这是无效的，
 * 根本没进程来读取，就不需要写入数据，直接kill，退出码就无效，会返回终止信号：13
 */

/**
 * 管道是有大小的：通过写段一直写数据，每次写1个字节数据，读端不读，就可以
 * 看到写道65536字节时就阻塞了，因为管道满了，写端进程要等待读端进程把管道的数据读走
 * ，写段进程才会被唤醒继续写数据。
 */

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <cstring>
#include <sys/wait.h>

int main()
{
    // 创建管道
    int fds[2] = {0};
    pid_t id = pipe(fds); // 函数参数是一个输出型参数：fds[0]：读端，fds[1]：写端
    if (id < 0)
    {
        perror("pipe error");
        exit(2);
    }
    // 创建子进程
    pid_t pid = fork();
    // 判断
    if (pid < 0)
    {
        // 创建子进程失败
        perror("fork error");
        exit(2);
    }
    else if (pid == 0)
    {
        // 子进程创建成功
        // 因为子进程进行写，所以先关闭读端:fds[0]是读端，fds[1]是写端
        close(fds[0]);

        // （测试写端进程一直不写，读端一直读：
        // 一开始管道数据没有，读端读不到数据，
        // 读端进程就阻塞在read处，即挂起，知道有数据就会唤醒）因此先sleep(5)
        //  sleep(5);

        int cnt = 5; // 写入数据的条数

        // // 测试管道大小，一次写一个字节，
        // char ch = 'z';
        // int byte_num = 0;
        // while (1)

        while (cnt--)
        {
            // 然后进行写入数据
            std::string message = "我是子进程：";
            message += std::to_string(getpid());
            message += "，第";
            message += std::to_string(cnt);
            message += "数据";

            // 调用系统接口write()  参数一：往哪写(此处往管道写端写数据)，参数二：写什么，参数三：写多长/少
            size_t num_w = write(fds[1], message.c_str(), message.size());

            // size_t num_w = write(fds[1], &ch, 1);
            // byte_num++;
            // std::cout << "字节数：" << byte_num << std::endl;

            if (num_w < 0)
            {
                // 返回-1，表示写入失败
                perror("write error");
                close(fds[1]);
                exit(2);
            }
            // 写入成功返回写入的字节数
            sleep(1);
        }
        // 数据写结束也要关闭
        close(fds[1]);
        // 退出
        exit(10);
    }
    // 父进程
    // 父进程进行读数据。先关闭写端
    close(fds[1]);
    // 定义空间存储读取数据
    char buffer[1024];
    // 循环读取
    while (1)
    {
        // （测试读端一开始不读，sleep一会，一段时间不读，写段一直写
        // 管道写满后，写端进程会被挂起，知道读端读取后会被唤醒）
        //  sleep(10);
        // sleep(5);

        // ssize_t read(int fd, void *buf, size_t count);//参数一：从哪里读，参数二：读到哪，写入定义的缓冲区（用户定义的缓冲区），参数三：读一次读多大数据
        // 读取成功返回读取的总字节数，失败返回-1
        size_t num_r = read(fds[0], buffer, 1024);
        // size_t num_r = read(fds[0], buffer, 65536);//测试管道最大容量，写满后读取后，观测写端写入管道的数据量

        if (num_r < 0)
        {
            perror("read error");
            break;
        }
        else if (num_r > 0)
        {
            // 即读取成功，由于读取的数据不知道末尾是否有\0，所以添加一个
            // 此时的num_r表示读取的字节数
            buffer[num_r] = '\0';
            // 打印出来
            std::cout << "子进程->父进程：" << buffer << std::endl;
        }
        else
        {
            // 此时num_r==0,表示已经读到结尾了
            std::cout << "read file end!" << std::endl;
            break;
        }
    }
    // 读取结束，关闭读端
    close(fds[0]);

    // 父进程等待子进程退出pid_t waitpid(pid_t pid, int *wstatus, int options);
    // 参数一：要等待的子进程的pid，参数二：输出型参数，保存子进程退出的状态，参数三：定义阻塞等待还是非阻塞等待，为0表示阻塞等待
    // 等待成功，返回子进程的pid
    int status = 0;
    waitpid(pid, &status, 0);
    std::cout << "子进程退出状态：" << WEXITSTATUS(status) << "，退出信号：" << (status & 0x7F) << std::endl;
    return 0;
}